﻿using System;
using System.Configuration;
using System.Drawing;
using Core.View.Renders;
using Core.Controllers;
using Core.Model;
using Core.Model.Chunks;
using Core.Model.Types;
using SharpDX.D3DCompiler;
using SharpDX.DXGI;
using SharpDX.Direct3D;
using SharpDX.Direct3D10;
using SharpDX.Game.Model;
using SharpDX.Game.Model.Helpers;
using SharpDX.Windows;
using Device = SharpDX.Direct3D10.Device;
using DriverType = SharpDX.Direct3D10.DriverType;
using Resource = SharpDX.Direct3D10.Resource;

namespace SharpDX.Game.View.Renders
{
    public class SharpDxWordRenderer : WorldRenderer
    {
        #region Static methods

        public static SharpDxWordRenderer Create()
        {
            RenderForm form = new RenderForm();

            form.Width = Convert.ToInt32(ConfigurationManager.AppSettings["width"]);
            form.Height = Convert.ToInt32(ConfigurationManager.AppSettings["height"]);

            SharpDxWordRenderer result = new SharpDxWordRenderer(form);

            result.Initialize();

            return
                result;
        }

        #endregion

        #region Fields

        private SwapChain swapChain;
        private RenderTargetView renderView;
        private Effect effect;
        private EffectPass pass;
        private InputLayout layout;
        private EffectMatrixVariable wvpVariable = null;
        private EffectMatrixVariable worldVariable = null;
        private EffectMatrixVariable viewVariable = null;
        private EffectMatrixVariable projectionVariable = null;
        private EffectVectorVariable indexVariable = null;
        private EffectVectorVariable cameraPositionVariable = null;
        private EffectShaderResourceVariable textureVariable = null;
        private EffectVectorVariable sunPositionVariable = null;
        private EffectScalarVariable timeOfDayVariable = null;

        private DepthStencilState DepthState;

        private DepthStencilView depthStencilView;
        #endregion

        #region ctor

        private SharpDxWordRenderer(RenderForm renderForm)
        {
            RenderForm = renderForm;
        }

        #endregion

        #region Properies

        public override IntPtr WindowHandle
        {
            get { return RenderForm.Handle; }
        }

        public override int WindowWidth
        {
            get { return RenderForm.ClientSize.Width; }
        }

        public override int WindowHeight
        {
            get { return RenderForm.ClientSize.Height; }
        }

        public RenderForm RenderForm;

        public Device _device;

        #endregion

        #region Public methods

        private Color4 HorizonColor = TypeCompatibleHelper.ConvertFromColor(Color.CadetBlue);

        public override void BeginDraw()
        {
            _device.ClearRenderTargetView(renderView, HorizonColor);
            _device.ClearDepthStencilView(depthStencilView, DepthStencilClearFlags.Depth | DepthStencilClearFlags.Stencil, 1, 0);

            //_device.OutputMerger.DepthStencilState = DepthState;            
        }

        public override void DrawTerrain(Camera camera, Chunk[] chunks)
        {
            wvpVariable.SetMatrix(camera.View * camera.Projection);

            worldVariable.SetMatrix(camera.World);
            viewVariable.SetMatrix(camera.View);
            projectionVariable.SetMatrix(camera.Projection);

            cameraPositionVariable.Set(camera.Position);
            sunPositionVariable.Set(camera.CameraWorld.SkyDomeRenderer.GetDirection());
            timeOfDayVariable.Set(camera.CameraWorld.SkyDomeRenderer.TimeOfDay);

            BoundingFrustumF viewFrustum = new BoundingFrustumF(camera.View * camera.Projection);

            int d = 0;

            for (int i = 0; i < chunks.Length; i++)
            {
                Chunk chunk = chunks[i];

                if (chunk == null || chunk.IsReady == false)
                {
                    continue;
                }

                // Проверяем расстояние (для отрисовки чанков по окружности)
                if (Vector3F.DistanceSquared(camera.PositionIndex.X, camera.PositionIndex.Z, chunk.Index.X, chunk.Index.Z) > ChunkManager.GENERATE_RANGE_HIGH_SQUARED)
                {
                    continue;
                }

                if (viewFrustum.FastIntersects(chunk.BoundingBox))
                {
                    indexVariable.Set(TypeCompatibleHelper.ConvertFromVector3F(chunk.Position3));

                    ((SharpDxChunk)chunk).DrawChunk(_device, pass);
                }
            }
        }

        public override void EndDraw()
        {
            swapChain.Present(0, PresentFlags.None);
        }

        #endregion

        #region Private methods

        private void Initialize()
        {
            var desc = new SwapChainDescription()
            {
                BufferCount = 1,
                ModeDescription =
                    new ModeDescription(WindowWidth, WindowHeight,
                                        new Rational(60, 1), Format.R8G8B8A8_UNorm),
                IsWindowed = true,
                OutputHandle = WindowHandle,
                SampleDescription = new SampleDescription(1, 0),
                SwapEffect = SwapEffect.Discard,
                Usage = Usage.RenderTargetOutput
            };

            Device.CreateWithSwapChain(DriverType.Hardware, DeviceCreationFlags.None, desc, out _device, out swapChain);

            //Stops Alt+enter from causing fullscreen skrewiness.
            Factory factory = swapChain.GetParent<Factory>();
            factory.MakeWindowAssociation(WindowHandle, WindowAssociationFlags.IgnoreAll);

            renderView = new RenderTargetView(_device, Resource.FromSwapChain<Texture2D>(swapChain, 0));
            
            //CreateDepthBuffer(RenderForm.Width, RenderForm.Height);

            

            depthStencilView = new DepthStencilView(_device, new Texture2D(_device, new Texture2DDescription()
            {
                Format = Format.D32_Float_S8X24_UInt,
                ArraySize = 1,
                MipLevels = 1,
                Width = WindowWidth,
                Height = WindowHeight,
                SampleDescription = new SampleDescription(1, 0),
                Usage = ResourceUsage.Default,
                BindFlags = BindFlags.DepthStencil,
                CpuAccessFlags = CpuAccessFlags.None,
                OptionFlags = ResourceOptionFlags.None
            }));

            //DepthStencilStateDescription stencilStateDesc = new DepthStencilStateDescription();
            //stencilStateDesc.IsDepthEnabled = true;
            //stencilStateDesc.IsStencilEnabled = true;
            //stencilStateDesc.DepthWriteMask = DepthWriteMask.All;
            //stencilStateDesc.DepthComparison = Comparison.Less;
            //stencilStateDesc.StencilReadMask = 0xFF;
            //stencilStateDesc.StencilWriteMask = 0xFF;

            //DepthStencilOperationDescription dsod = new DepthStencilOperationDescription();

            //dsod.FailOperation = StencilOperation.Keep;
            //dsod.DepthFailOperation = StencilOperation.Increment;
            //dsod.PassOperation = StencilOperation.Keep;
            //dsod.Comparison = Comparison.Always;
            //dsod.DepthFailOperation = StencilOperation.Decrement;

            //stencilStateDesc.FrontFace = dsod;
            //stencilStateDesc.BackFace = dsod;

            //DepthState = new DepthStencilState(_device, stencilStateDesc);

            var effectByteCode = ShaderBytecode.CompileFromFile("Content/Shaders/triangle.fx", "fx_4_0", ShaderFlags.EnableBackwardsCompatibility, EffectFlags.None);
            effect = new Effect(_device, effectByteCode);

            var technique = effect.GetTechniqueByIndex(0);
            pass = technique.GetPassByIndex(0);

            wvpVariable = effect.GetVariableByName("WVP").AsMatrix();
            worldVariable = effect.GetVariableByName("World").AsMatrix();
            viewVariable = effect.GetVariableByName("View").AsMatrix();
            projectionVariable = effect.GetVariableByName("Projection").AsMatrix();
            indexVariable = effect.GetVariableByName("Index").AsVector();
            cameraPositionVariable = effect.GetVariableByName("CameraPosition").AsVector();
            textureVariable = effect.GetVariableByName("AtlasTexture").AsShaderResource();
            sunPositionVariable = effect.GetVariableByName("g_LightDir").AsVector();
            timeOfDayVariable = effect.GetVariableByName("timeOfDay").AsScalar();

            effect.GetVariableByName("HorizonColor").AsVector().Set(TypeCompatibleHelper.ConvertFromColor(World.HORIZONCOLOR));
            effect.GetVariableByName("NightColor").AsVector().Set(TypeCompatibleHelper.ConvertFromColor(World.NIGHTCOLOR));
            effect.GetVariableByName("MorningTint").AsVector().Set(TypeCompatibleHelper.ConvertFromColor(World.MORNINGTINT));
            effect.GetVariableByName("EveningTint").AsVector().Set(TypeCompatibleHelper.ConvertFromColor(World.EVENINGTINT));
            effect.GetVariableByName("SunColor").AsVector().Set(TypeCompatibleHelper.ConvertFromColor(World.SUNCOLOR));
            //effect.GetVariableByName("FogNear").AsScalar().Set(ChunkManager.FOGNEAR);
            //effect.GetVariableByName("FogFar").AsScalar().Set(ChunkManager.FOGFAR);

            ShaderResourceView resource = new ShaderResourceView(_device, 
                Texture2D.FromFile<Texture2D>(_device, @"Content\Textures\terrain.png"));

            textureVariable.SetResource(resource);

            layout = new InputLayout(_device, pass.Description.Signature, BlockVertex.InputElements);

            _device.InputAssembler.InputLayout = layout;
            _device.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;

            _device.Rasterizer.SetViewports(new Viewport(0, 0, WindowWidth, WindowHeight, 0.0f, 1.0f));

            _device.OutputMerger.SetTargets(depthStencilView, renderView);
        }

        private void CreateDepthBuffer(int width, int height)
        {
            Texture2D texDesc = new Texture2D(_device, new Texture2DDescription()
            {
                Format = Format.D32_Float_S8X24_UInt,
                ArraySize = 1,
                MipLevels = 1,
                Width = width,
                Height = height,
                SampleDescription = new SampleDescription(1, 0),
                Usage = ResourceUsage.Default,
                BindFlags = BindFlags.DepthStencil,
                CpuAccessFlags = CpuAccessFlags.None,
                OptionFlags = ResourceOptionFlags.None
            });

            depthStencilView = new DepthStencilView(_device, texDesc);

            
            //DepthStencilViewDescription stencilViewDesc = new DepthStencilViewDescription();

            //stencilViewDesc.Dimension = DepthStencilViewDimension.Texture2D;
            //stencilViewDesc.Format = texDesc.Format;
            //stencilViewDesc.MipSlice = 0;
            //stencilViewDesc.FirstArraySlice = 0;
            //stencilViewDesc.ArraySize = 1;

            //using (Texture2D stencilTex = new Texture2D(_device, texDesc))
            //{
            //    depthStencilView = new DepthStencilView(_device, stencilTex, stencilViewDesc);
            //}

            DepthStencilStateDescription stencilStateDesc = new DepthStencilStateDescription();
            stencilStateDesc.IsDepthEnabled = true;
            stencilStateDesc.IsStencilEnabled = true;
            stencilStateDesc.DepthWriteMask = DepthWriteMask.All;
            stencilStateDesc.DepthComparison = Comparison.Less;
            stencilStateDesc.StencilReadMask = 0xFF;
            stencilStateDesc.StencilWriteMask = 0xFF;

            DepthStencilOperationDescription dsod = new DepthStencilOperationDescription();

            dsod.FailOperation = StencilOperation.Keep;
            dsod.DepthFailOperation = StencilOperation.Increment;
            dsod.PassOperation = StencilOperation.Keep;
            dsod.Comparison = Comparison.Always;
            stencilStateDesc.FrontFace = dsod;
            dsod.DepthFailOperation = StencilOperation.Decrement;
            stencilStateDesc.BackFace = dsod;

            DepthState = new DepthStencilState(_device, stencilStateDesc);

            RasterizerStateDescription rsd = new RasterizerStateDescription();
            rsd.FillMode = FillMode.Solid;
            rsd.CullMode = CullMode.Back;
            rsd.IsMultisampleEnabled = true;
            rsd.IsAntialiasedLineEnabled = false;
            rsd.IsDepthClipEnabled = false;
            rsd.IsScissorEnabled = false;

            _device.Rasterizer.State = new RasterizerState(_device, rsd);

            //BlendStateDescription blendDesc = new BlendStateDescription();
            //blendDesc.BlendOperation = BlendOperation.Add;
            //blendDesc.AlphaBlendOperation = BlendOperation.Add;
            //blendDesc.SourceAlphaBlend = BlendOption.Zero;
            //blendDesc.DestinationAlphaBlend = BlendOption.Zero;
            //blendDesc.SourceBlend = BlendOption.SourceColor;
            //blendDesc.DestinationBlend = BlendOption.Zero;
            //blendDesc.IsAlphaToCoverageEnabled = false;
            //blendDesc.SetWriteMask(0, ColorWriteMaskFlags.All);
            //blendDesc.SetBlendEnable(0, true);

            //_device.OutputMerger.SetBlendState(new BlendState(_device, blendDesc), new Color4(1, 1,1,1), 0);
            //_device.OutputMerger.BlendState = new BlendState(_device, blendDesc);
        }

        #endregion

       
    }
}
